In this analysis module, we have annotated cell types in SCPCP000004 samples using two methods: SingleR and scANVI/scArches, both using the NBAtlas dataset as a reference. In this notebook, we derive the final annotations using information from the SingleR labels, the scANVI/scArches labels, and existing consensus cell type annotations. All final annotations use labels present in the NBAtlas dataset.

We use the following approach to determine the final annotation. Throughout, we use CL ontology ids to perform comparisons. For more information about how these ontology ids were determined for NBAtlas labels, see ../references/README.md.

  1. If SingleR and scANVI/scArches labels exactly agree, we assign that label
  2. If broad label families (e.g., T cell would be the broad family for CD4+ T cell) for SingleR and scANVI/scArches labels agree, we assign the broad family label.
  1. If SingleR and scANVI/scArches labels disagree, we then compare to the consensus cell type; if at least one inference agrees with the consensus cell type, we assign the inferred label.
  1. Finally, following the conclusions of Bonine et al. (2024), we designate any cell labeled as Neuroendocrine as a tumor cell.

Setup

suppressPackageStartupMessages({
  library(ggplot2)
  library(patchwork)
  library(SingleCellExperiment)
})

theme_set(
  theme_bw()
)
umap_theme <- list(
  theme(
    axis.text = element_blank(), 
    axis.ticks = element_blank(), 
    legend.position = "bottom", 
    aspect.ratio = 1
  )
)

# settings
options(
  dplyr.summarise.inform = FALSE, 
  readr.show_col_types = FALSE
)

Paths

module_dir <- rprojroot::find_root(rprojroot::is_renv_project)
repository_base <- file.path(module_dir, "..", "..")

data_dir <- file.path(repository_base, "data", "current", "SCPCP000004")
merged_dir <- file.path(repository_base, "data", "current", "results", "merge-sce", "SCPCP000004")
ref_dir <- file.path(module_dir, "references")
results_dir <- file.path(module_dir, "results")
singler_dir <- file.path(results_dir, "singler")
scanvi_dir <- file.path(results_dir, "scanvi")
# merged SCE file
sce_file <- file.path(
  merged_dir,
  "SCPCP000004_merged.rds"
)

# SingleR files
singler_files <- list.files(
  path = singler_dir,
  pattern = "_singler-annotations\\.tsv",
  recursive = TRUE,
  full.names = TRUE
) |>
  # add names as library id
  purrr::set_names(
    \(x) {
      stringr::str_remove(basename(x), "_singler-annotations.tsv")
    }
  )

# scanvi predictions
scanvi_file <- file.path(
  scanvi_dir, 
  "scanvi-predictions.tsv"
)

# palette file
palette_file <- file.path(
  module_dir,
  "palettes",
  "nbatlas-cell-type-palette.tsv"
)

# label map file
nbatlas_label_map_file <- file.path(
  ref_dir, 
  "nbatlas-label-map.tsv"
)

# label ontologies file
nbatlas_ontology_file <- file.path(
  ref_dir, 
  "nbatlas-ontology-ids.tsv"
)

# marker genes from NBAtlas
nbatlas_markers_file <- file.path(
  ref_dir, 
  "nbatlas-marker-genes.tsv"
)

# validation groups URL, used to obtain consensus "family" ontologies (aka broad validation group ontologies)
validation_url <- "https://raw.githubusercontent.com/AlexsLemonade/OpenScPCA-analysis/refs/heads/main/analyses/cell-type-consensus/references/consensus-validation-groups.tsv"

Functions

# Source utilities functions:
# - subset_nbatlas_markers()
# - faceted_umap()
# - generate_dotplot()
source(file.path(module_dir, "scripts", "utils", "celltype-utils.R"))
#' Prepare data frame of scANVI or SingleR labels for annotation
#'
#' @param df Data frame to prepare
#' @param annot_type "singler" or "scanvi"
#' @param ontology_df Data frame of ontology ids
#' @param label_map_df Data frame mapping labels to family labels
#'
#' @returns Wide data frame with ontologies and family labels for the given annotation type
prep_for_annotation <- function(
    df, 
    annot_type, 
    ontology_df, 
    label_map_df) {

  df |>
    dplyr::select(all_of(c("cell_id", annot_type))) |>
    dplyr::rename(label = annot_type) |>
    ####### Join in the family labels
    dplyr::left_join(label_map_df, by = c("label" = "NBAtlas_label")) |>
    dplyr::rename(family = NBAtlas_family) |>
    ######### Obtain LABEL ontologies
    dplyr::left_join(ontology_slim_df, by = c("label" = "NBAtlas_label")) |>
    dplyr::rename(label_ontology = CL_ontology_id) |>
    ######## Obtain FAMILY ontologies
    dplyr::left_join(ontology_slim_df, by = c("family" = "NBAtlas_label")) |>
    dplyr::rename(family_ontology = CL_ontology_id) |>
    # rename columns to start with `annot_type`
    dplyr::rename_with(\(x) {paste(annot_type, x, sep = "_")}, -cell_id) 
}

Read and prepare input data

Read merged SCE object:

merged_sce <- readRDS(sce_file)
# immediately remove assays we don't need for space
assay(merged_sce, "spliced") <- NULL
assay(merged_sce, "counts") <- NULL
reducedDim(merged_sce, "PCA") <- NULL

Read SingleR results:

singler_df <- singler_files |>
  purrr::map(readr::read_tsv) |>
  purrr::list_rbind(names_to = "library_id") |>
  dplyr::mutate(
    # add cell id column for unique rows & joining
    cell_id = glue::glue("{library_id}-{barcodes}"),
    # recode NA -> "Unknown" and NE -> "Neuroendocrine"
    singler = dplyr::case_when(
      is.na(pruned.labels) ~ "Unknown", 
      pruned.labels == "NE" ~ "Neuroendocrine",
      .default = pruned.labels)
  ) |>
  dplyr::select(cell_id, singler)

Read scANVI results:

scanvi_df <- readr::read_tsv(
  file.path(scanvi_dir, "scanvi_predictions.tsv")
) |>
  dplyr::select(
    cell_id, 
    scanvi = scanvi_prediction, 
    starts_with("pp_")
  ) |>
  tidyr::pivot_longer(
    starts_with("pp_"), 
    names_to = "posterior_celltype", 
    values_to = "posterior"
  ) |>
  dplyr::mutate(posterior_celltype = stringr::str_remove(posterior_celltype, "^pp_")) |>
  dplyr::filter(scanvi == posterior_celltype) |>
  # recode to Unknown if below the threshold, and NE -> Neuroendocrine
  dplyr::mutate(scanvi = dplyr::case_when(
    posterior < params$scanvi_pp_threshold ~ "Unknown", 
    scanvi == "NE" ~ "Neuroendocrine", 
    .default = scanvi)) |>
  dplyr::select(cell_id, scanvi)

Read additional helper files:

palette_df <- readr::read_tsv(palette_file)
celltype_pal <- palette_df$hex_color
names(celltype_pal) <- palette_df$NBAtlas_label

label_map_df <- readr::read_tsv(nbatlas_label_map_file)
ontology_df <- readr::read_tsv(nbatlas_ontology_file)

nbatlas_markers_df <- readr::read_tsv(nbatlas_markers_file)

validation_df <- readr::read_tsv(validation_url) |>
  dplyr::rename(
    consensus = consensus_annotation,
    consensus_family_label = validation_group_annotation,
    consensus_family_ontology = validation_group_ontology 
  )

Combine annotations into a single data frame:

celltype_df <- scuttle::makePerCellDF(
  merged_sce,
  use.coldata = c("sample_id", "is_xenograft", "consensus_celltype_annotation", "consensus_celltype_ontology")
) |>
  tibble::rownames_to_column("cell_id") |>
  tidyr::separate(cell_id, into = c("library_id", "cell_barcode"), remove = FALSE) |>
  # keep UMAP for eventual viz
  dplyr::rename(
    consensus = consensus_celltype_annotation, 
    consensus_ontology = consensus_celltype_ontology,
    UMAP1 = UMAP.1, 
    UMAP2 = UMAP.2
  ) |>
  dplyr::left_join(singler_df, by = "cell_id") |>
  dplyr::left_join(scanvi_df, by = "cell_id")


# Pull out metadata we'll want later
sample_metadata <- celltype_df |>
  dplyr::select(sample_id, library_id, is_xenograft) |>
  unique()

Annotate

Firt we prepare the data frame for annotation.

ontology_slim_df <- ontology_df |>
  dplyr::select(-CL_annotation)

singler_annotation_df <- prep_for_annotation(
  celltype_df, 
  "singler", 
  ontology_slim_df, 
  label_map_df
)
scanvi_annotation_df <- prep_for_annotation(
  celltype_df, 
  "scanvi", 
  ontology_slim_df, 
  label_map_df
)

annotation_df <- celltype_df |>
  dplyr::select(cell_id, consensus, consensus_ontology) |>
  dplyr::left_join(singler_annotation_df, by = "cell_id") |>
  dplyr::left_join(scanvi_annotation_df, by = "cell_id") |>
  dplyr::left_join(validation_df, by = c("consensus", "consensus_ontology")) |>
  dplyr::select(cell_id, starts_with("consensus"), starts_with("singler"), starts_with("scanvi"))

In the code below, we compare ontology ids to derive final annotations. However, based on those comparisons, we assign a final label, not final ontology id, and then add the final ontology ids back into those annotations. This is because we have several NA ontology ids, and we would not be able to unambiguously map their labels in if we assigned ontologies.

final_annotation_df <- annotation_df |> 
  dplyr::mutate(
    # For all comparisons, make sure we aren't comparing NA to NA and getting TRUE
    final_label = dplyr::case_when(
      ########### Check for exact match between SingleR/scANVI
      !is.na(scanvi_label_ontology) & singler_label_ontology == scanvi_label_ontology ~ scanvi_label, 
      ########### Check for family match between SingleR/scANVI
      !is.na(scanvi_family_ontology) & singler_family_ontology == scanvi_family_ontology ~ scanvi_family,
      ########## Now use agreement with consensus to assign a label: first by label, and then by family
      !is.na(consensus_ontology) & singler_label_ontology  == consensus_ontology ~ singler_label,
      !is.na(consensus_ontology) & scanvi_label_ontology   == consensus_ontology ~ scanvi_label,
      !is.na(consensus_family_ontology) & singler_family_ontology == consensus_family_ontology ~ singler_family,
      !is.na(consensus_family_ontology) & scanvi_family_ontology  == consensus_family_ontology ~ scanvi_family,
      # Everything else is Unknown
      .default = "Unknown"
    )
  ) |>
  # Now, we can bring map the final ontologies into the data frame
  dplyr::inner_join(
    ontology_df |> dplyr::rename(final_label = NBAtlas_label, final_id = CL_ontology_id), 
    by = "final_label"
  ) |>
  # add tumor column for visualization
  dplyr::mutate(cell_class = dplyr::case_when(
    final_label == "Neuroendocrine" ~ "tumor", 
    final_label == "Unknown" ~ "unknown", 
    .default = "normal"
  ))

Explore final annotations

What fraction of cells have been annotated?

sum(!is.na(final_annotation_df$final_id)) / nrow(final_annotation_df)
[1] 0.8506399

What fraction of cells have been annotated per library?

frac_labeled_df <- final_annotation_df |>
  tidyr::separate(cell_id, into = c("library_id", "barcode"), remove = FALSE) |>
  dplyr::group_by(library_id) |>
  dplyr::summarize(
    frac_labeled = sum(!is.na(final_id))/dplyr::n(), 
    library_size = dplyr::n()
  ) |>
  # get PDX logical for coloring
  dplyr::inner_join(sample_metadata, by = "library_id")


p1 <- ggplot(frac_labeled_df) + 
  aes(x = frac_labeled) + 
  geom_histogram(bins = 40) +
  labs(x = "Fraction of library labeled") +
  theme_bw()


p2 <- ggplot(frac_labeled_df) + 
  aes(x = library_size, y = frac_labeled, color = is_xenograft) + 
  geom_point() + 
  labs(
    x = "Total cells in library",
    y = "Fraction of library labeled"
  ) +
  theme_bw() +
  theme(legend.position = "bottom")


p1 + p2

UMAPs

In this section, we show the UMAP from the merged (not batch-corrected!) SCPCP000004 object colored by cell type annotations and other potentially relevant metadata.

label_order <- label_map_df |>
  dplyr::add_count(NBAtlas_family) |>
  dplyr::arrange(desc(n), NBAtlas_family) |>
  dplyr::filter(NBAtlas_label %in% final_annotation_df$final_label) |>
  dplyr::pull(NBAtlas_label)
label_order <- c(label_order, "Unknown")

# prepare data frame for plotting
umap_df <- celltype_df |>
  dplyr::select(cell_id, sample_id, library_id, is_xenograft, UMAP1, UMAP2) |>
  dplyr::left_join(final_annotation_df, by = "cell_id") |>
  dplyr::mutate(final_label = factor(final_label, levels = label_order))

Colored by cell type assignment

Note that in the UMAP below, cell types in these same “family” each share a color:

  • T cells
  • Natural killer cells
  • Conventional dendritic cells
  • Monocytes
ggplot(umap_df) +
  aes(x = UMAP1, y = UMAP2, color = final_label) +
  geom_point(alpha = 0.2, size = 0.3) +
  scale_color_manual(values = celltype_pal) +
  guides(color = guide_legend(override.aes = list(size = 2, alpha = 1))) + 
  umap_theme +
  theme(legend.position = "bottom")

Faceted by cell type assignment

This UMAP is the same as the previous one, except each facet highlights one specific cell type. In this plot, only cell types with at least 10 cells are shown.

umap_df |>
  dplyr::add_count(final_label) |>
  dplyr::filter(n > 10) |>
  faceted_umap(
    annotation_column = final_label,
    celltype_colors = celltype_pal, 
    facet_wrap_cols = 6
  ) + 
    umap_theme +
    theme(legend.position = "none")

Colored by tumor classification

ggplot(umap_df) +
  aes(x = UMAP1, y = UMAP2, color = cell_class) +
  geom_point(alpha = 0.2, size = 0.3) +
  scale_color_brewer(palette = "Set2") +
  guides(color = guide_legend(override.aes = list(size = 2, alpha = 1))) +
  umap_theme

Colored by PDX status

ggplot(umap_df) +
  aes(x = UMAP1, y = UMAP2, color = is_xenograft) +
  geom_point(alpha = 0.2, size = 0.3) +
  scale_color_brewer(palette = "Set1") +
  guides(color = guide_legend(override.aes = list(size = 2, alpha = 1))) +
  umap_theme

Dot plot

This section shows dot plots of marker gene expression for the final annotations. Specifically, we show the top 5 highest log2FC marker genes per NBAtlas cell type for validation. Marker genes are taken directly from the NBAtlas paper where they were identified with Seurat::FindMarkers().

Marker genes that are present in three or more cell types are not included. In addition, the upper limit for the color scale has been censored to a maximum of 6 to ensure visibility of expression levels across all cell types.

input_dotplot_df <- final_annotation_df |>
  dplyr::filter(final_label != "Unknown") |>
  # recode cell types to match what is present in NBAtlas marker genes
  dplyr::mutate(
    label_recoded = dplyr::case_when(
      stringr::str_detect(final_label, "monocyte") ~ "Monocyte", 
      stringr::str_detect(final_label, "cDC") ~ "cDC",
      .default = final_label
  )) |>
  dplyr::select(cell_id, label_recoded)

# data frame of top 5 upregulated genes per cell type
top_nbatlas_markers_df <- subset_nbatlas_markers(nbatlas_markers_df, 5, "up") |>
  # only markers in no more than 2 categories
  # this is a good middle ground to ensure there are at least 2 markers per cell type
  dplyr::add_count(ensembl_gene_id) |>
  dplyr::filter(n < 3) |>
  dplyr::select(-n) 
# get total number of cells per final annotation group and set up y_label
total_cells_df <- input_dotplot_df |>
  dplyr::count(label_recoded, name = "total_cells") |>
  dplyr::mutate(y_label = glue::glue("{label_recoded} ({total_cells})")) |>
  dplyr::inner_join(label_map_df, by = c("label_recoded" = "NBAtlas_label")) |>
  dplyr::group_by(NBAtlas_family) |>
  dplyr::mutate(family_total_cells = sum(total_cells)) |>
  # order by family count, but put Unknown at the end
  dplyr::arrange(desc(family_total_cells)) |>
  # amazing new dplyr trick; only this row is TRUE, so it gets moved to the last row
  dplyr::arrange(label_recoded == "Unknown")

total_cells_df$y_label <- factor(total_cells_df$y_label, levels = rev(total_cells_df$y_label))
nbatlas_bar_order <- total_cells_df$label_recoded
# Prepare the expressed_genes vector
# we only care about if that gene is expressed otherwise we won't waste memory and include it
gene_sums <- rowData(merged_sce) |>
  as.data.frame() |>
  dplyr::select(contains("detected")) |>
  as.matrix() |>
  rowSums()
expressed_genes <- names(gene_sums)[gene_sums > 0]
generate_dotplot(
  merged_sce = merged_sce,
  markers_df = top_nbatlas_markers_df,
  celltype_df = input_dotplot_df,
  total_cells_df = total_cells_df,
  expressed_genes = expressed_genes,
  bar_order = nbatlas_bar_order, 
  celltype_palette = celltype_pal, 
  min_cells = 10 # only show cell types with at least 10 cells
) +
  theme(legend.position = "bottom")

Export

Export the final table with submission-ready column names.

final_annotation_df |>
  dplyr::select(
    cell_id, 
    cell_type_assignment = final_label, 
    tumor_cell_classification = cell_class,
    CL_ontology_id = final_id, 
    CL_annotation
  ) |>
  readr::write_tsv(
    params$annotations_tsv
  )

Session Info

sessionInfo()
R version 4.4.0 (2024-04-24)
Platform: aarch64-apple-darwin20
Running under: macOS 15.6

Matrix products: default
BLAS:   /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRblas.0.dylib 
LAPACK: /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRlapack.dylib;  LAPACK version 3.12.0

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

time zone: America/New_York
tzcode source: internal

attached base packages:
[1] stats4    stats     graphics  grDevices datasets  utils     methods  
[8] base     

other attached packages:
 [1] SingleCellExperiment_1.26.0 SummarizedExperiment_1.34.0
 [3] Biobase_2.64.0              GenomicRanges_1.56.2       
 [5] GenomeInfoDb_1.40.1         IRanges_2.38.1             
 [7] S4Vectors_0.42.1            BiocGenerics_0.50.0        
 [9] MatrixGenerics_1.16.0       matrixStats_1.5.0          
[11] patchwork_1.3.0             ggplot2_3.5.2              

loaded via a namespace (and not attached):
 [1] gtable_0.3.6              xfun_0.52                
 [3] bslib_0.9.0               lattice_0.22-6           
 [5] tzdb_0.5.0                bitops_1.0-9             
 [7] vctrs_0.6.5               tools_4.4.0              
 [9] generics_0.1.4            curl_6.3.0               
[11] parallel_4.4.0            tibble_3.3.0             
[13] pkgconfig_2.0.3           Matrix_1.7-1             
[15] RColorBrewer_1.1-3        sparseMatrixStats_1.16.0 
[17] lifecycle_1.0.4           GenomeInfoDbData_1.2.12  
[19] compiler_4.4.0            farver_2.1.2             
[21] stringr_1.5.1             codetools_0.2-20         
[23] htmltools_0.5.8.1         sass_0.4.10              
[25] yaml_2.3.10               pillar_1.10.2            
[27] crayon_1.5.3              jquerylib_0.1.4          
[29] tidyr_1.3.1               BiocParallel_1.38.0      
[31] DelayedArray_0.30.1       cachem_1.1.0             
[33] abind_1.4-8               tidyselect_1.2.1         
[35] digest_0.6.37             stringi_1.8.7            
[37] dplyr_1.1.4               purrr_1.0.4              
[39] labeling_0.4.3            rprojroot_2.0.4          
[41] fastmap_1.2.0             grid_4.4.0               
[43] cli_3.6.5                 SparseArray_1.4.8        
[45] magrittr_2.0.3            S4Arrays_1.4.1           
[47] readr_2.1.5               withr_3.0.2              
[49] DelayedMatrixStats_1.26.0 scales_1.4.0             
[51] UCSC.utils_1.0.0          bit64_4.6.0-1            
[53] rmarkdown_2.29            XVector_0.44.0           
[55] httr_1.4.7                jpeg_0.1-11              
[57] ggmap_4.0.1               bit_4.6.0                
[59] png_0.1-8                 hms_1.1.3                
[61] beachmat_2.20.0           evaluate_1.0.3           
[63] knitr_1.50                viridisLite_0.4.2        
[65] rlang_1.1.6               Rcpp_1.0.14              
[67] scuttle_1.14.0            glue_1.8.0               
[69] BiocManager_1.30.26       renv_1.1.3               
[71] vroom_1.6.5               jsonlite_2.0.0           
[73] plyr_1.8.9                R6_2.6.1                 
[75] zlibbioc_1.50.0          
LS0tCnRpdGxlOiAiRGVyaXZlIGZpbmFsIGFubm90YXRpb25zIGJhc2VkIG9uIE5CQXRsYXMgcmVmZXJlbmNlIgphdXRob3I6ICJTdGVwaGFuaWUgSi4gU3BpZWxtYW4iCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IHRydWUKICAgIHRvY19kZXB0aDogMwogICAgY29kZV9mb2xkaW5nOiBoaWRlCnBhcmFtczoKICBzY2FudmlfcHBfdGhyZXNob2xkOiAwLjc1ICAgIAogIGFubm90YXRpb25zX3RzdjogInJlc3VsdHMvU0NQQ1AwMDAwMDQtYW5ub3RhdGlvbnMudHN2IgogIHRlc3Rpbmc6IEZBTFNFCi0tLQoKSW4gdGhpcyBhbmFseXNpcyBtb2R1bGUsIHdlIGhhdmUgYW5ub3RhdGVkIGNlbGwgdHlwZXMgaW4gYFNDUENQMDAwMDA0YCBzYW1wbGVzIHVzaW5nIHR3byBtZXRob2RzOiBgU2luZ2xlUmAgYW5kIGBzY0FOVkkvc2NBcmNoZXNgLCBib3RoIHVzaW5nIHRoZSBOQkF0bGFzIGRhdGFzZXQgYXMgYSByZWZlcmVuY2UuCkluIHRoaXMgbm90ZWJvb2ssIHdlIGRlcml2ZSB0aGUgZmluYWwgYW5ub3RhdGlvbnMgdXNpbmcgaW5mb3JtYXRpb24gZnJvbSB0aGUgYFNpbmdsZVJgIGxhYmVscywgdGhlIGBzY0FOVkkvc2NBcmNoZXNgIGxhYmVscywgYW5kIGV4aXN0aW5nIGNvbnNlbnN1cyBjZWxsIHR5cGUgYW5ub3RhdGlvbnMuCkFsbCBmaW5hbCBhbm5vdGF0aW9ucyB1c2UgbGFiZWxzIHByZXNlbnQgaW4gdGhlIE5CQXRsYXMgZGF0YXNldC4KCldlIHVzZSB0aGUgZm9sbG93aW5nIGFwcHJvYWNoIHRvIGRldGVybWluZSB0aGUgZmluYWwgYW5ub3RhdGlvbi4KVGhyb3VnaG91dCwgd2UgdXNlIENMIG9udG9sb2d5IGlkcyB0byBwZXJmb3JtIGNvbXBhcmlzb25zLiAKRm9yIG1vcmUgaW5mb3JtYXRpb24gYWJvdXQgaG93IHRoZXNlIG9udG9sb2d5IGlkcyB3ZXJlIGRldGVybWluZWQgZm9yIE5CQXRsYXMgbGFiZWxzLCBzZWUgYC4uL3JlZmVyZW5jZXMvUkVBRE1FLm1kYC4KCjEuIElmIGBTaW5nbGVSYCBhbmQgYHNjQU5WSS9zY0FyY2hlc2AgbGFiZWxzIGV4YWN0bHkgYWdyZWUsIHdlIGFzc2lnbiB0aGF0IGxhYmVsCjIuIElmIGJyb2FkIGxhYmVsIGZhbWlsaWVzIChlLmcuLCBgVCBjZWxsYCB3b3VsZCBiZSB0aGUgYnJvYWQgZmFtaWx5IGZvciBgQ0Q0KyBUIGNlbGxgKSBmb3IgYFNpbmdsZVJgIGFuZCBgc2NBTlZJL3NjQXJjaGVzYCBsYWJlbHMgYWdyZWUsIHdlIGFzc2lnbiB0aGUgYnJvYWQgZmFtaWx5IGxhYmVsLgogICogU2VlIFtgcmVmZXJlbmNlcy9uYmF0bGFzLWxhYmVsLW1hcC50c3ZgXShyZWZlcmVuY2VzL25iYXRsYXMtbGFiZWwtbWFwLnRzdikgYW5kIFtgcmVmZXJlbmNlcy9SRUFETUUubWRgXShyZWZlcmVuY2VzL1JFQURNRS5tZCkgZm9yIGhvdyBsYWJlbHMgd2VyZSBtYXBwZWQgdG8gZmFtaWxpZXMuCjMuIElmIGBTaW5nbGVSYCBhbmQgYHNjQU5WSS9zY0FyY2hlc2AgbGFiZWxzIGRpc2FncmVlLCB3ZSB0aGVuIGNvbXBhcmUgdG8gdGhlIGNvbnNlbnN1cyBjZWxsIHR5cGU7IGlmIGF0IGxlYXN0IG9uZSBpbmZlcmVuY2UgYWdyZWVzIHdpdGggdGhlIGNvbnNlbnN1cyBjZWxsIHR5cGUsIHdlIGFzc2lnbiB0aGUgaW5mZXJyZWQgbGFiZWwuCiAgKiBXZSBwZXJmb3JtIHRoaXMgZmlyc3QgYXQgdGhlIGxhYmVsIGxldmVsIGFuZCB0aGVuIGF0IHRoZSBicm9hZCBmYW1pbHkgbGV2ZWwuCjQuIEZpbmFsbHksIGZvbGxvd2luZyB0aGUgY29uY2x1c2lvbnMgb2YgQm9uaW5lIGV0IGFsLiAoMjAyNCksIHdlIGRlc2lnbmF0ZSBhbnkgY2VsbCBsYWJlbGVkIGFzIGBOZXVyb2VuZG9jcmluZWAgYXMgYSB0dW1vciBjZWxsLgoKCiMjIFNldHVwCgpgYGB7ciwgd2FybmluZyA9IEZBTFNFfQpzdXBwcmVzc1BhY2thZ2VTdGFydHVwTWVzc2FnZXMoewogIGxpYnJhcnkoZ2dwbG90MikKICBsaWJyYXJ5KHBhdGNod29yaykKICBsaWJyYXJ5KFNpbmdsZUNlbGxFeHBlcmltZW50KQp9KQoKdGhlbWVfc2V0KAogIHRoZW1lX2J3KCkKKQp1bWFwX3RoZW1lIDwtIGxpc3QoCiAgdGhlbWUoCiAgICBheGlzLnRleHQgPSBlbGVtZW50X2JsYW5rKCksIAogICAgYXhpcy50aWNrcyA9IGVsZW1lbnRfYmxhbmsoKSwgCiAgICBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwgCiAgICBhc3BlY3QucmF0aW8gPSAxCiAgKQopCgojIHNldHRpbmdzCm9wdGlvbnMoCiAgZHBseXIuc3VtbWFyaXNlLmluZm9ybSA9IEZBTFNFLCAKICByZWFkci5zaG93X2NvbF90eXBlcyA9IEZBTFNFCikKYGBgCgojIyMgUGF0aHMKCmBgYHtyIGJhc2UgcGF0aHN9Cm1vZHVsZV9kaXIgPC0gcnByb2pyb290OjpmaW5kX3Jvb3QocnByb2pyb290Ojppc19yZW52X3Byb2plY3QpCnJlcG9zaXRvcnlfYmFzZSA8LSBmaWxlLnBhdGgobW9kdWxlX2RpciwgIi4uIiwgIi4uIikKCmRhdGFfZGlyIDwtIGZpbGUucGF0aChyZXBvc2l0b3J5X2Jhc2UsICJkYXRhIiwgImN1cnJlbnQiLCAiU0NQQ1AwMDAwMDQiKQptZXJnZWRfZGlyIDwtIGZpbGUucGF0aChyZXBvc2l0b3J5X2Jhc2UsICJkYXRhIiwgImN1cnJlbnQiLCAicmVzdWx0cyIsICJtZXJnZS1zY2UiLCAiU0NQQ1AwMDAwMDQiKQpyZWZfZGlyIDwtIGZpbGUucGF0aChtb2R1bGVfZGlyLCAicmVmZXJlbmNlcyIpCnJlc3VsdHNfZGlyIDwtIGZpbGUucGF0aChtb2R1bGVfZGlyLCAicmVzdWx0cyIpCnNpbmdsZXJfZGlyIDwtIGZpbGUucGF0aChyZXN1bHRzX2RpciwgInNpbmdsZXIiKQpzY2FudmlfZGlyIDwtIGZpbGUucGF0aChyZXN1bHRzX2RpciwgInNjYW52aSIpCmBgYAoKYGBge3IgZmlsZSBwYXRoc30KIyBtZXJnZWQgU0NFIGZpbGUKc2NlX2ZpbGUgPC0gZmlsZS5wYXRoKAogIG1lcmdlZF9kaXIsCiAgIlNDUENQMDAwMDA0X21lcmdlZC5yZHMiCikKCiMgU2luZ2xlUiBmaWxlcwpzaW5nbGVyX2ZpbGVzIDwtIGxpc3QuZmlsZXMoCiAgcGF0aCA9IHNpbmdsZXJfZGlyLAogIHBhdHRlcm4gPSAiX3NpbmdsZXItYW5ub3RhdGlvbnNcXC50c3YiLAogIHJlY3Vyc2l2ZSA9IFRSVUUsCiAgZnVsbC5uYW1lcyA9IFRSVUUKKSB8PgogICMgYWRkIG5hbWVzIGFzIGxpYnJhcnkgaWQKICBwdXJycjo6c2V0X25hbWVzKAogICAgXCh4KSB7CiAgICAgIHN0cmluZ3I6OnN0cl9yZW1vdmUoYmFzZW5hbWUoeCksICJfc2luZ2xlci1hbm5vdGF0aW9ucy50c3YiKQogICAgfQogICkKCiMgc2NhbnZpIHByZWRpY3Rpb25zCnNjYW52aV9maWxlIDwtIGZpbGUucGF0aCgKICBzY2FudmlfZGlyLCAKICAic2NhbnZpLXByZWRpY3Rpb25zLnRzdiIKKQoKIyBwYWxldHRlIGZpbGUKcGFsZXR0ZV9maWxlIDwtIGZpbGUucGF0aCgKICBtb2R1bGVfZGlyLAogICJwYWxldHRlcyIsCiAgIm5iYXRsYXMtY2VsbC10eXBlLXBhbGV0dGUudHN2IgopCgojIGxhYmVsIG1hcCBmaWxlCm5iYXRsYXNfbGFiZWxfbWFwX2ZpbGUgPC0gZmlsZS5wYXRoKAogIHJlZl9kaXIsIAogICJuYmF0bGFzLWxhYmVsLW1hcC50c3YiCikKCiMgbGFiZWwgb250b2xvZ2llcyBmaWxlCm5iYXRsYXNfb250b2xvZ3lfZmlsZSA8LSBmaWxlLnBhdGgoCiAgcmVmX2RpciwgCiAgIm5iYXRsYXMtb250b2xvZ3ktaWRzLnRzdiIKKQoKIyBtYXJrZXIgZ2VuZXMgZnJvbSBOQkF0bGFzCm5iYXRsYXNfbWFya2Vyc19maWxlIDwtIGZpbGUucGF0aCgKICByZWZfZGlyLCAKICAibmJhdGxhcy1tYXJrZXItZ2VuZXMudHN2IgopCgojIHZhbGlkYXRpb24gZ3JvdXBzIFVSTCwgdXNlZCB0byBvYnRhaW4gY29uc2Vuc3VzICJmYW1pbHkiIG9udG9sb2dpZXMgKGFrYSBicm9hZCB2YWxpZGF0aW9uIGdyb3VwIG9udG9sb2dpZXMpCnZhbGlkYXRpb25fdXJsIDwtICJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vQWxleHNMZW1vbmFkZS9PcGVuU2NQQ0EtYW5hbHlzaXMvcmVmcy9oZWFkcy9tYWluL2FuYWx5c2VzL2NlbGwtdHlwZS1jb25zZW5zdXMvcmVmZXJlbmNlcy9jb25zZW5zdXMtdmFsaWRhdGlvbi1ncm91cHMudHN2IgpgYGAKCgojIyMgRnVuY3Rpb25zCgpgYGB7cn0KIyBTb3VyY2UgdXRpbGl0aWVzIGZ1bmN0aW9uczoKIyAtIHN1YnNldF9uYmF0bGFzX21hcmtlcnMoKQojIC0gZmFjZXRlZF91bWFwKCkKIyAtIGdlbmVyYXRlX2RvdHBsb3QoKQpzb3VyY2UoZmlsZS5wYXRoKG1vZHVsZV9kaXIsICJzY3JpcHRzIiwgInV0aWxzIiwgImNlbGx0eXBlLXV0aWxzLlIiKSkKYGBgCgoKYGBge3J9CgojJyBQcmVwYXJlIGRhdGEgZnJhbWUgb2Ygc2NBTlZJIG9yIFNpbmdsZVIgbGFiZWxzIGZvciBhbm5vdGF0aW9uCiMnCiMnIEBwYXJhbSBkZiBEYXRhIGZyYW1lIHRvIHByZXBhcmUKIycgQHBhcmFtIGFubm90X3R5cGUgInNpbmdsZXIiIG9yICJzY2FudmkiCiMnIEBwYXJhbSBvbnRvbG9neV9kZiBEYXRhIGZyYW1lIG9mIG9udG9sb2d5IGlkcwojJyBAcGFyYW0gbGFiZWxfbWFwX2RmIERhdGEgZnJhbWUgbWFwcGluZyBsYWJlbHMgdG8gZmFtaWx5IGxhYmVscwojJwojJyBAcmV0dXJucyBXaWRlIGRhdGEgZnJhbWUgd2l0aCBvbnRvbG9naWVzIGFuZCBmYW1pbHkgbGFiZWxzIGZvciB0aGUgZ2l2ZW4gYW5ub3RhdGlvbiB0eXBlCnByZXBfZm9yX2Fubm90YXRpb24gPC0gZnVuY3Rpb24oCiAgICBkZiwgCiAgICBhbm5vdF90eXBlLCAKICAgIG9udG9sb2d5X2RmLCAKICAgIGxhYmVsX21hcF9kZikgewoKICBkZiB8PgogICAgZHBseXI6OnNlbGVjdChhbGxfb2YoYygiY2VsbF9pZCIsIGFubm90X3R5cGUpKSkgfD4KICAgIGRwbHlyOjpyZW5hbWUobGFiZWwgPSBhbm5vdF90eXBlKSB8PgogICAgIyMjIyMjIyBKb2luIGluIHRoZSBmYW1pbHkgbGFiZWxzCiAgICBkcGx5cjo6bGVmdF9qb2luKGxhYmVsX21hcF9kZiwgYnkgPSBjKCJsYWJlbCIgPSAiTkJBdGxhc19sYWJlbCIpKSB8PgogICAgZHBseXI6OnJlbmFtZShmYW1pbHkgPSBOQkF0bGFzX2ZhbWlseSkgfD4KICAgICMjIyMjIyMjIyBPYnRhaW4gTEFCRUwgb250b2xvZ2llcwogICAgZHBseXI6OmxlZnRfam9pbihvbnRvbG9neV9zbGltX2RmLCBieSA9IGMoImxhYmVsIiA9ICJOQkF0bGFzX2xhYmVsIikpIHw+CiAgICBkcGx5cjo6cmVuYW1lKGxhYmVsX29udG9sb2d5ID0gQ0xfb250b2xvZ3lfaWQpIHw+CiAgICAjIyMjIyMjIyBPYnRhaW4gRkFNSUxZIG9udG9sb2dpZXMKICAgIGRwbHlyOjpsZWZ0X2pvaW4ob250b2xvZ3lfc2xpbV9kZiwgYnkgPSBjKCJmYW1pbHkiID0gIk5CQXRsYXNfbGFiZWwiKSkgfD4KICAgIGRwbHlyOjpyZW5hbWUoZmFtaWx5X29udG9sb2d5ID0gQ0xfb250b2xvZ3lfaWQpIHw+CiAgICAjIHJlbmFtZSBjb2x1bW5zIHRvIHN0YXJ0IHdpdGggYGFubm90X3R5cGVgCiAgICBkcGx5cjo6cmVuYW1lX3dpdGgoXCh4KSB7cGFzdGUoYW5ub3RfdHlwZSwgeCwgc2VwID0gIl8iKX0sIC1jZWxsX2lkKSAKfQpgYGAKCgojIyMgUmVhZCBhbmQgcHJlcGFyZSBpbnB1dCBkYXRhCgpSZWFkIG1lcmdlZCBTQ0Ugb2JqZWN0OgoKYGBge3J9Cm1lcmdlZF9zY2UgPC0gcmVhZFJEUyhzY2VfZmlsZSkKIyBpbW1lZGlhdGVseSByZW1vdmUgYXNzYXlzIHdlIGRvbid0IG5lZWQgZm9yIHNwYWNlCmFzc2F5KG1lcmdlZF9zY2UsICJzcGxpY2VkIikgPC0gTlVMTAphc3NheShtZXJnZWRfc2NlLCAiY291bnRzIikgPC0gTlVMTApyZWR1Y2VkRGltKG1lcmdlZF9zY2UsICJQQ0EiKSA8LSBOVUxMCmBgYAoKUmVhZCBgU2luZ2xlUmAgcmVzdWx0czoKCmBgYHtyfQpzaW5nbGVyX2RmIDwtIHNpbmdsZXJfZmlsZXMgfD4KICBwdXJycjo6bWFwKHJlYWRyOjpyZWFkX3RzdikgfD4KICBwdXJycjo6bGlzdF9yYmluZChuYW1lc190byA9ICJsaWJyYXJ5X2lkIikgfD4KICBkcGx5cjo6bXV0YXRlKAogICAgIyBhZGQgY2VsbCBpZCBjb2x1bW4gZm9yIHVuaXF1ZSByb3dzICYgam9pbmluZwogICAgY2VsbF9pZCA9IGdsdWU6OmdsdWUoIntsaWJyYXJ5X2lkfS17YmFyY29kZXN9IiksCiAgICAjIHJlY29kZSBOQSAtPiAiVW5rbm93biIgYW5kIE5FIC0+ICJOZXVyb2VuZG9jcmluZSIKICAgIHNpbmdsZXIgPSBkcGx5cjo6Y2FzZV93aGVuKAogICAgICBpcy5uYShwcnVuZWQubGFiZWxzKSB+ICJVbmtub3duIiwgCiAgICAgIHBydW5lZC5sYWJlbHMgPT0gIk5FIiB+ICJOZXVyb2VuZG9jcmluZSIsCiAgICAgIC5kZWZhdWx0ID0gcHJ1bmVkLmxhYmVscykKICApIHw+CiAgZHBseXI6OnNlbGVjdChjZWxsX2lkLCBzaW5nbGVyKQpgYGAKClJlYWQgYHNjQU5WSWAgcmVzdWx0czoKCmBgYHtyfQpzY2FudmlfZGYgPC0gcmVhZHI6OnJlYWRfdHN2KAogIGZpbGUucGF0aChzY2FudmlfZGlyLCAic2NhbnZpX3ByZWRpY3Rpb25zLnRzdiIpCikgfD4KICBkcGx5cjo6c2VsZWN0KAogICAgY2VsbF9pZCwgCiAgICBzY2FudmkgPSBzY2FudmlfcHJlZGljdGlvbiwgCiAgICBzdGFydHNfd2l0aCgicHBfIikKICApIHw+CiAgdGlkeXI6OnBpdm90X2xvbmdlcigKICAgIHN0YXJ0c193aXRoKCJwcF8iKSwgCiAgICBuYW1lc190byA9ICJwb3N0ZXJpb3JfY2VsbHR5cGUiLCAKICAgIHZhbHVlc190byA9ICJwb3N0ZXJpb3IiCiAgKSB8PgogIGRwbHlyOjptdXRhdGUocG9zdGVyaW9yX2NlbGx0eXBlID0gc3RyaW5ncjo6c3RyX3JlbW92ZShwb3N0ZXJpb3JfY2VsbHR5cGUsICJecHBfIikpIHw+CiAgZHBseXI6OmZpbHRlcihzY2FudmkgPT0gcG9zdGVyaW9yX2NlbGx0eXBlKSB8PgogICMgcmVjb2RlIHRvIFVua25vd24gaWYgYmVsb3cgdGhlIHRocmVzaG9sZCwgYW5kIE5FIC0+IE5ldXJvZW5kb2NyaW5lCiAgZHBseXI6Om11dGF0ZShzY2FudmkgPSBkcGx5cjo6Y2FzZV93aGVuKAogICAgcG9zdGVyaW9yIDwgcGFyYW1zJHNjYW52aV9wcF90aHJlc2hvbGQgfiAiVW5rbm93biIsIAogICAgc2NhbnZpID09ICJORSIgfiAiTmV1cm9lbmRvY3JpbmUiLCAKICAgIC5kZWZhdWx0ID0gc2NhbnZpKSkgfD4KICBkcGx5cjo6c2VsZWN0KGNlbGxfaWQsIHNjYW52aSkKYGBgCgpSZWFkIGFkZGl0aW9uYWwgaGVscGVyIGZpbGVzOgoKYGBge3J9CnBhbGV0dGVfZGYgPC0gcmVhZHI6OnJlYWRfdHN2KHBhbGV0dGVfZmlsZSkKY2VsbHR5cGVfcGFsIDwtIHBhbGV0dGVfZGYkaGV4X2NvbG9yCm5hbWVzKGNlbGx0eXBlX3BhbCkgPC0gcGFsZXR0ZV9kZiROQkF0bGFzX2xhYmVsCgpsYWJlbF9tYXBfZGYgPC0gcmVhZHI6OnJlYWRfdHN2KG5iYXRsYXNfbGFiZWxfbWFwX2ZpbGUpCm9udG9sb2d5X2RmIDwtIHJlYWRyOjpyZWFkX3RzdihuYmF0bGFzX29udG9sb2d5X2ZpbGUpCgpuYmF0bGFzX21hcmtlcnNfZGYgPC0gcmVhZHI6OnJlYWRfdHN2KG5iYXRsYXNfbWFya2Vyc19maWxlKQoKdmFsaWRhdGlvbl9kZiA8LSByZWFkcjo6cmVhZF90c3YodmFsaWRhdGlvbl91cmwpIHw+CiAgZHBseXI6OnJlbmFtZSgKICAgIGNvbnNlbnN1cyA9IGNvbnNlbnN1c19hbm5vdGF0aW9uLAogICAgY29uc2Vuc3VzX2ZhbWlseV9sYWJlbCA9IHZhbGlkYXRpb25fZ3JvdXBfYW5ub3RhdGlvbiwKICAgIGNvbnNlbnN1c19mYW1pbHlfb250b2xvZ3kgPSB2YWxpZGF0aW9uX2dyb3VwX29udG9sb2d5IAogICkKYGBgCgoKQ29tYmluZSBhbm5vdGF0aW9ucyBpbnRvIGEgc2luZ2xlIGRhdGEgZnJhbWU6CgpgYGB7cn0KY2VsbHR5cGVfZGYgPC0gc2N1dHRsZTo6bWFrZVBlckNlbGxERigKICBtZXJnZWRfc2NlLAogIHVzZS5jb2xkYXRhID0gYygic2FtcGxlX2lkIiwgImlzX3hlbm9ncmFmdCIsICJjb25zZW5zdXNfY2VsbHR5cGVfYW5ub3RhdGlvbiIsICJjb25zZW5zdXNfY2VsbHR5cGVfb250b2xvZ3kiKQopIHw+CiAgdGliYmxlOjpyb3duYW1lc190b19jb2x1bW4oImNlbGxfaWQiKSB8PgogIHRpZHlyOjpzZXBhcmF0ZShjZWxsX2lkLCBpbnRvID0gYygibGlicmFyeV9pZCIsICJjZWxsX2JhcmNvZGUiKSwgcmVtb3ZlID0gRkFMU0UpIHw+CiAgIyBrZWVwIFVNQVAgZm9yIGV2ZW50dWFsIHZpegogIGRwbHlyOjpyZW5hbWUoCiAgICBjb25zZW5zdXMgPSBjb25zZW5zdXNfY2VsbHR5cGVfYW5ub3RhdGlvbiwgCiAgICBjb25zZW5zdXNfb250b2xvZ3kgPSBjb25zZW5zdXNfY2VsbHR5cGVfb250b2xvZ3ksCiAgICBVTUFQMSA9IFVNQVAuMSwgCiAgICBVTUFQMiA9IFVNQVAuMgogICkgfD4KICBkcGx5cjo6bGVmdF9qb2luKHNpbmdsZXJfZGYsIGJ5ID0gImNlbGxfaWQiKSB8PgogIGRwbHlyOjpsZWZ0X2pvaW4oc2NhbnZpX2RmLCBieSA9ICJjZWxsX2lkIikKCgojIFB1bGwgb3V0IG1ldGFkYXRhIHdlJ2xsIHdhbnQgbGF0ZXIKc2FtcGxlX21ldGFkYXRhIDwtIGNlbGx0eXBlX2RmIHw+CiAgZHBseXI6OnNlbGVjdChzYW1wbGVfaWQsIGxpYnJhcnlfaWQsIGlzX3hlbm9ncmFmdCkgfD4KICB1bmlxdWUoKQpgYGAKCgojIyBBbm5vdGF0ZQoKRmlydCB3ZSBwcmVwYXJlIHRoZSBkYXRhIGZyYW1lIGZvciBhbm5vdGF0aW9uLgoKYGBge3IsIHdhcm5pbmcgPSBGQUxTRX0Kb250b2xvZ3lfc2xpbV9kZiA8LSBvbnRvbG9neV9kZiB8PgogIGRwbHlyOjpzZWxlY3QoLUNMX2Fubm90YXRpb24pCgpzaW5nbGVyX2Fubm90YXRpb25fZGYgPC0gcHJlcF9mb3JfYW5ub3RhdGlvbigKICBjZWxsdHlwZV9kZiwgCiAgInNpbmdsZXIiLCAKICBvbnRvbG9neV9zbGltX2RmLCAKICBsYWJlbF9tYXBfZGYKKQpzY2FudmlfYW5ub3RhdGlvbl9kZiA8LSBwcmVwX2Zvcl9hbm5vdGF0aW9uKAogIGNlbGx0eXBlX2RmLCAKICAic2NhbnZpIiwgCiAgb250b2xvZ3lfc2xpbV9kZiwgCiAgbGFiZWxfbWFwX2RmCikKCmFubm90YXRpb25fZGYgPC0gY2VsbHR5cGVfZGYgfD4KICBkcGx5cjo6c2VsZWN0KGNlbGxfaWQsIGNvbnNlbnN1cywgY29uc2Vuc3VzX29udG9sb2d5KSB8PgogIGRwbHlyOjpsZWZ0X2pvaW4oc2luZ2xlcl9hbm5vdGF0aW9uX2RmLCBieSA9ICJjZWxsX2lkIikgfD4KICBkcGx5cjo6bGVmdF9qb2luKHNjYW52aV9hbm5vdGF0aW9uX2RmLCBieSA9ICJjZWxsX2lkIikgfD4KICBkcGx5cjo6bGVmdF9qb2luKHZhbGlkYXRpb25fZGYsIGJ5ID0gYygiY29uc2Vuc3VzIiwgImNvbnNlbnN1c19vbnRvbG9neSIpKSB8PgogIGRwbHlyOjpzZWxlY3QoY2VsbF9pZCwgc3RhcnRzX3dpdGgoImNvbnNlbnN1cyIpLCBzdGFydHNfd2l0aCgic2luZ2xlciIpLCBzdGFydHNfd2l0aCgic2NhbnZpIikpCmBgYAoKCkluIHRoZSBjb2RlIGJlbG93LCB3ZSBjb21wYXJlIG9udG9sb2d5IGlkcyB0byBkZXJpdmUgZmluYWwgYW5ub3RhdGlvbnMuCkhvd2V2ZXIsIGJhc2VkIG9uIHRob3NlIGNvbXBhcmlzb25zLCB3ZSBhc3NpZ24gYSBmaW5hbCBfbGFiZWxfLCBub3QgZmluYWwgb250b2xvZ3kgaWQsIGFuZCB0aGVuIGFkZCB0aGUgZmluYWwgb250b2xvZ3kgaWRzIGJhY2sgaW50byB0aG9zZSBhbm5vdGF0aW9ucy4KVGhpcyBpcyBiZWNhdXNlIHdlIGhhdmUgc2V2ZXJhbCBgTkFgIG9udG9sb2d5IGlkcywgYW5kIHdlIHdvdWxkIG5vdCBiZSBhYmxlIHRvIHVuYW1iaWd1b3VzbHkgbWFwIHRoZWlyIGxhYmVscyBpbiBpZiB3ZSBhc3NpZ25lZCBvbnRvbG9naWVzLgoKYGBge3J9CmZpbmFsX2Fubm90YXRpb25fZGYgPC0gYW5ub3RhdGlvbl9kZiB8PiAKICBkcGx5cjo6bXV0YXRlKAogICAgIyBGb3IgYWxsIGNvbXBhcmlzb25zLCBtYWtlIHN1cmUgd2UgYXJlbid0IGNvbXBhcmluZyBOQSB0byBOQSBhbmQgZ2V0dGluZyBUUlVFCiAgICBmaW5hbF9sYWJlbCA9IGRwbHlyOjpjYXNlX3doZW4oCiAgICAgICMjIyMjIyMjIyMjIENoZWNrIGZvciBleGFjdCBtYXRjaCBiZXR3ZWVuIFNpbmdsZVIvc2NBTlZJCiAgICAgICFpcy5uYShzY2FudmlfbGFiZWxfb250b2xvZ3kpICYgc2luZ2xlcl9sYWJlbF9vbnRvbG9neSA9PSBzY2FudmlfbGFiZWxfb250b2xvZ3kgfiBzY2FudmlfbGFiZWwsIAogICAgICAjIyMjIyMjIyMjIyBDaGVjayBmb3IgZmFtaWx5IG1hdGNoIGJldHdlZW4gU2luZ2xlUi9zY0FOVkkKICAgICAgIWlzLm5hKHNjYW52aV9mYW1pbHlfb250b2xvZ3kpICYgc2luZ2xlcl9mYW1pbHlfb250b2xvZ3kgPT0gc2NhbnZpX2ZhbWlseV9vbnRvbG9neSB+IHNjYW52aV9mYW1pbHksCiAgICAgICMjIyMjIyMjIyMgTm93IHVzZSBhZ3JlZW1lbnQgd2l0aCBjb25zZW5zdXMgdG8gYXNzaWduIGEgbGFiZWw6IGZpcnN0IGJ5IGxhYmVsLCBhbmQgdGhlbiBieSBmYW1pbHkKICAgICAgIWlzLm5hKGNvbnNlbnN1c19vbnRvbG9neSkgJiBzaW5nbGVyX2xhYmVsX29udG9sb2d5ICA9PSBjb25zZW5zdXNfb250b2xvZ3kgfiBzaW5nbGVyX2xhYmVsLAogICAgICAhaXMubmEoY29uc2Vuc3VzX29udG9sb2d5KSAmIHNjYW52aV9sYWJlbF9vbnRvbG9neSAgID09IGNvbnNlbnN1c19vbnRvbG9neSB+IHNjYW52aV9sYWJlbCwKICAgICAgIWlzLm5hKGNvbnNlbnN1c19mYW1pbHlfb250b2xvZ3kpICYgc2luZ2xlcl9mYW1pbHlfb250b2xvZ3kgPT0gY29uc2Vuc3VzX2ZhbWlseV9vbnRvbG9neSB+IHNpbmdsZXJfZmFtaWx5LAogICAgICAhaXMubmEoY29uc2Vuc3VzX2ZhbWlseV9vbnRvbG9neSkgJiBzY2FudmlfZmFtaWx5X29udG9sb2d5ICA9PSBjb25zZW5zdXNfZmFtaWx5X29udG9sb2d5IH4gc2NhbnZpX2ZhbWlseSwKICAgICAgIyBFdmVyeXRoaW5nIGVsc2UgaXMgVW5rbm93bgogICAgICAuZGVmYXVsdCA9ICJVbmtub3duIgogICAgKQogICkgfD4KICAjIE5vdywgd2UgY2FuIGJyaW5nIG1hcCB0aGUgZmluYWwgb250b2xvZ2llcyBpbnRvIHRoZSBkYXRhIGZyYW1lCiAgZHBseXI6OmlubmVyX2pvaW4oCiAgICBvbnRvbG9neV9kZiB8PiBkcGx5cjo6cmVuYW1lKGZpbmFsX2xhYmVsID0gTkJBdGxhc19sYWJlbCwgZmluYWxfaWQgPSBDTF9vbnRvbG9neV9pZCksIAogICAgYnkgPSAiZmluYWxfbGFiZWwiCiAgKSB8PgogICMgYWRkIHR1bW9yIGNvbHVtbiBmb3IgdmlzdWFsaXphdGlvbgogIGRwbHlyOjptdXRhdGUoY2VsbF9jbGFzcyA9IGRwbHlyOjpjYXNlX3doZW4oCiAgICBmaW5hbF9sYWJlbCA9PSAiTmV1cm9lbmRvY3JpbmUiIH4gInR1bW9yIiwgCiAgICBmaW5hbF9sYWJlbCA9PSAiVW5rbm93biIgfiAidW5rbm93biIsIAogICAgLmRlZmF1bHQgPSAibm9ybWFsIgogICkpCmBgYCAKCgojIyBFeHBsb3JlIGZpbmFsIGFubm90YXRpb25zCgpXaGF0IGZyYWN0aW9uIG9mIGNlbGxzIGhhdmUgYmVlbiBhbm5vdGF0ZWQ/CgoKYGBge3J9CnN1bSghaXMubmEoZmluYWxfYW5ub3RhdGlvbl9kZiRmaW5hbF9pZCkpIC8gbnJvdyhmaW5hbF9hbm5vdGF0aW9uX2RmKQpgYGAKCgpXaGF0IGZyYWN0aW9uIG9mIGNlbGxzIGhhdmUgYmVlbiBhbm5vdGF0ZWQgcGVyIGxpYnJhcnk/CgpgYGB7ciwgZmlnLndpZHRoID0gOCwgZmlnLmhlaWdodCA9IDR9CmZyYWNfbGFiZWxlZF9kZiA8LSBmaW5hbF9hbm5vdGF0aW9uX2RmIHw+CiAgdGlkeXI6OnNlcGFyYXRlKGNlbGxfaWQsIGludG8gPSBjKCJsaWJyYXJ5X2lkIiwgImJhcmNvZGUiKSwgcmVtb3ZlID0gRkFMU0UpIHw+CiAgZHBseXI6Omdyb3VwX2J5KGxpYnJhcnlfaWQpIHw+CiAgZHBseXI6OnN1bW1hcml6ZSgKICAgIGZyYWNfbGFiZWxlZCA9IHN1bSghaXMubmEoZmluYWxfaWQpKS9kcGx5cjo6bigpLCAKICAgIGxpYnJhcnlfc2l6ZSA9IGRwbHlyOjpuKCkKICApIHw+CiAgIyBnZXQgUERYIGxvZ2ljYWwgZm9yIGNvbG9yaW5nCiAgZHBseXI6OmlubmVyX2pvaW4oc2FtcGxlX21ldGFkYXRhLCBieSA9ICJsaWJyYXJ5X2lkIikKCgpwMSA8LSBnZ3Bsb3QoZnJhY19sYWJlbGVkX2RmKSArIAogIGFlcyh4ID0gZnJhY19sYWJlbGVkKSArIAogIGdlb21faGlzdG9ncmFtKGJpbnMgPSA0MCkgKwogIGxhYnMoeCA9ICJGcmFjdGlvbiBvZiBsaWJyYXJ5IGxhYmVsZWQiKSArCiAgdGhlbWVfYncoKQoKCnAyIDwtIGdncGxvdChmcmFjX2xhYmVsZWRfZGYpICsgCiAgYWVzKHggPSBsaWJyYXJ5X3NpemUsIHkgPSBmcmFjX2xhYmVsZWQsIGNvbG9yID0gaXNfeGVub2dyYWZ0KSArIAogIGdlb21fcG9pbnQoKSArIAogIGxhYnMoCiAgICB4ID0gIlRvdGFsIGNlbGxzIGluIGxpYnJhcnkiLAogICAgeSA9ICJGcmFjdGlvbiBvZiBsaWJyYXJ5IGxhYmVsZWQiCiAgKSArCiAgdGhlbWVfYncoKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpCgoKcDEgKyBwMgpgYGAKCiMjIyBVTUFQcwoKSW4gdGhpcyBzZWN0aW9uLCB3ZSBzaG93IHRoZSBVTUFQIGZyb20gdGhlIG1lcmdlZCAobm90IGJhdGNoLWNvcnJlY3RlZCEpIGBTQ1BDUDAwMDAwNGAgb2JqZWN0IGNvbG9yZWQgYnkgY2VsbCB0eXBlIGFubm90YXRpb25zIGFuZCBvdGhlciBwb3RlbnRpYWxseSByZWxldmFudCBtZXRhZGF0YS4KCgpgYGB7cn0KbGFiZWxfb3JkZXIgPC0gbGFiZWxfbWFwX2RmIHw+CiAgZHBseXI6OmFkZF9jb3VudChOQkF0bGFzX2ZhbWlseSkgfD4KICBkcGx5cjo6YXJyYW5nZShkZXNjKG4pLCBOQkF0bGFzX2ZhbWlseSkgfD4KICBkcGx5cjo6ZmlsdGVyKE5CQXRsYXNfbGFiZWwgJWluJSBmaW5hbF9hbm5vdGF0aW9uX2RmJGZpbmFsX2xhYmVsKSB8PgogIGRwbHlyOjpwdWxsKE5CQXRsYXNfbGFiZWwpCmxhYmVsX29yZGVyIDwtIGMobGFiZWxfb3JkZXIsICJVbmtub3duIikKCiMgcHJlcGFyZSBkYXRhIGZyYW1lIGZvciBwbG90dGluZwp1bWFwX2RmIDwtIGNlbGx0eXBlX2RmIHw+CiAgZHBseXI6OnNlbGVjdChjZWxsX2lkLCBzYW1wbGVfaWQsIGxpYnJhcnlfaWQsIGlzX3hlbm9ncmFmdCwgVU1BUDEsIFVNQVAyKSB8PgogIGRwbHlyOjpsZWZ0X2pvaW4oZmluYWxfYW5ub3RhdGlvbl9kZiwgYnkgPSAiY2VsbF9pZCIpIHw+CiAgZHBseXI6Om11dGF0ZShmaW5hbF9sYWJlbCA9IGZhY3RvcihmaW5hbF9sYWJlbCwgbGV2ZWxzID0gbGFiZWxfb3JkZXIpKQpgYGAKCgojIyMjIENvbG9yZWQgYnkgY2VsbCB0eXBlIGFzc2lnbm1lbnQKCk5vdGUgdGhhdCBpbiB0aGUgVU1BUCBiZWxvdywgY2VsbCB0eXBlcyBpbiB0aGVzZSBzYW1lICJmYW1pbHkiIGVhY2ggc2hhcmUgYSBjb2xvcjoKCiogVCBjZWxscwoqIE5hdHVyYWwga2lsbGVyIGNlbGxzCiogQ29udmVudGlvbmFsIGRlbmRyaXRpYyBjZWxscwoqIE1vbm9jeXRlcwoKYGBge3IgZmlnLndpZHRoID0gMTAsIGZpZy5oZWlnaHQgPSAxMH0KZ2dwbG90KHVtYXBfZGYpICsKICBhZXMoeCA9IFVNQVAxLCB5ID0gVU1BUDIsIGNvbG9yID0gZmluYWxfbGFiZWwpICsKICBnZW9tX3BvaW50KGFscGhhID0gMC4yLCBzaXplID0gMC4zKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGNlbGx0eXBlX3BhbCkgKwogIGd1aWRlcyhjb2xvciA9IGd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KHNpemUgPSAyLCBhbHBoYSA9IDEpKSkgKyAKICB1bWFwX3RoZW1lICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikKYGBgCgojIyMjIEZhY2V0ZWQgYnkgY2VsbCB0eXBlIGFzc2lnbm1lbnQKClRoaXMgVU1BUCBpcyB0aGUgc2FtZSBhcyB0aGUgcHJldmlvdXMgb25lLCBleGNlcHQgZWFjaCBmYWNldCBoaWdobGlnaHRzIG9uZSBzcGVjaWZpYyBjZWxsIHR5cGUuCkluIHRoaXMgcGxvdCwgb25seSBjZWxsIHR5cGVzIHdpdGggYXQgbGVhc3QgMTAgY2VsbHMgYXJlIHNob3duLgoKCmBgYHtyIGZpZy53aWR0aCA9IDE2LCBmaWcuaGVpZ2h0ID0gMTZ9CnVtYXBfZGYgfD4KICBkcGx5cjo6YWRkX2NvdW50KGZpbmFsX2xhYmVsKSB8PgogIGRwbHlyOjpmaWx0ZXIobiA+IDEwKSB8PgogIGZhY2V0ZWRfdW1hcCgKICAgIGFubm90YXRpb25fY29sdW1uID0gZmluYWxfbGFiZWwsCiAgICBjZWxsdHlwZV9jb2xvcnMgPSBjZWxsdHlwZV9wYWwsIAogICAgZmFjZXRfd3JhcF9jb2xzID0gNgogICkgKyAKICAgIHVtYXBfdGhlbWUgKwogICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQpgYGAKCgojIyMjIENvbG9yZWQgYnkgdHVtb3IgY2xhc3NpZmljYXRpb24KCgpgYGB7ciBmaWcud2lkdGggPSA4LCBmaWcuaGVpZ2h0ID0gOH0KZ2dwbG90KHVtYXBfZGYpICsKICBhZXMoeCA9IFVNQVAxLCB5ID0gVU1BUDIsIGNvbG9yID0gY2VsbF9jbGFzcykgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjIsIHNpemUgPSAwLjMpICsKICBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZSA9ICJTZXQyIikgKwogIGd1aWRlcyhjb2xvciA9IGd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KHNpemUgPSAyLCBhbHBoYSA9IDEpKSkgKwogIHVtYXBfdGhlbWUKYGBgCgojIyMjIENvbG9yZWQgYnkgUERYIHN0YXR1cwoKYGBge3IgZmlnLndpZHRoID0gOCwgZmlnLmhlaWdodCA9IDh9CmdncGxvdCh1bWFwX2RmKSArCiAgYWVzKHggPSBVTUFQMSwgeSA9IFVNQVAyLCBjb2xvciA9IGlzX3hlbm9ncmFmdCkgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjIsIHNpemUgPSAwLjMpICsKICBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZSA9ICJTZXQxIikgKwogIGd1aWRlcyhjb2xvciA9IGd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KHNpemUgPSAyLCBhbHBoYSA9IDEpKSkgKwogIHVtYXBfdGhlbWUKYGBgCgoKIyMjIERvdCBwbG90CgpUaGlzIHNlY3Rpb24gc2hvd3MgZG90IHBsb3RzIG9mIG1hcmtlciBnZW5lIGV4cHJlc3Npb24gZm9yIHRoZSBmaW5hbCBhbm5vdGF0aW9ucy4KU3BlY2lmaWNhbGx5LCB3ZSBzaG93IHRoZSB0b3AgNSBoaWdoZXN0IGBsb2cyRkNgIG1hcmtlciBnZW5lcyBwZXIgTkJBdGxhcyBjZWxsIHR5cGUgZm9yIHZhbGlkYXRpb24uCk1hcmtlciBnZW5lcyBhcmUgdGFrZW4gZGlyZWN0bHkgZnJvbSB0aGUgTkJBdGxhcyBwYXBlciB3aGVyZSB0aGV5IHdlcmUgaWRlbnRpZmllZCB3aXRoIGBTZXVyYXQ6OkZpbmRNYXJrZXJzKClgLiAKCk1hcmtlciBnZW5lcyB0aGF0IGFyZSBwcmVzZW50IGluIHRocmVlIG9yIG1vcmUgY2VsbCB0eXBlcyBhcmUgbm90IGluY2x1ZGVkLgpJbiBhZGRpdGlvbiwgdGhlIHVwcGVyIGxpbWl0IGZvciB0aGUgY29sb3Igc2NhbGUgaGFzIGJlZW4gY2Vuc29yZWQgdG8gYSBtYXhpbXVtIG9mIDYgdG8gZW5zdXJlIHZpc2liaWxpdHkgb2YgZXhwcmVzc2lvbiBsZXZlbHMgYWNyb3NzIGFsbCBjZWxsIHR5cGVzLgoKYGBge3J9CmlucHV0X2RvdHBsb3RfZGYgPC0gZmluYWxfYW5ub3RhdGlvbl9kZiB8PgogIGRwbHlyOjpmaWx0ZXIoZmluYWxfbGFiZWwgIT0gIlVua25vd24iKSB8PgogICMgcmVjb2RlIGNlbGwgdHlwZXMgdG8gbWF0Y2ggd2hhdCBpcyBwcmVzZW50IGluIE5CQXRsYXMgbWFya2VyIGdlbmVzCiAgZHBseXI6Om11dGF0ZSgKICAgIGxhYmVsX3JlY29kZWQgPSBkcGx5cjo6Y2FzZV93aGVuKAogICAgICBzdHJpbmdyOjpzdHJfZGV0ZWN0KGZpbmFsX2xhYmVsLCAibW9ub2N5dGUiKSB+ICJNb25vY3l0ZSIsIAogICAgICBzdHJpbmdyOjpzdHJfZGV0ZWN0KGZpbmFsX2xhYmVsLCAiY0RDIikgfiAiY0RDIiwKICAgICAgLmRlZmF1bHQgPSBmaW5hbF9sYWJlbAogICkpIHw+CiAgZHBseXI6OnNlbGVjdChjZWxsX2lkLCBsYWJlbF9yZWNvZGVkKQoKIyBkYXRhIGZyYW1lIG9mIHRvcCA1IHVwcmVndWxhdGVkIGdlbmVzIHBlciBjZWxsIHR5cGUKdG9wX25iYXRsYXNfbWFya2Vyc19kZiA8LSBzdWJzZXRfbmJhdGxhc19tYXJrZXJzKG5iYXRsYXNfbWFya2Vyc19kZiwgNSwgInVwIikgfD4KICAjIG9ubHkgbWFya2VycyBpbiBubyBtb3JlIHRoYW4gMiBjYXRlZ29yaWVzCiAgIyB0aGlzIGlzIGEgZ29vZCBtaWRkbGUgZ3JvdW5kIHRvIGVuc3VyZSB0aGVyZSBhcmUgYXQgbGVhc3QgMiBtYXJrZXJzIHBlciBjZWxsIHR5cGUKICBkcGx5cjo6YWRkX2NvdW50KGVuc2VtYmxfZ2VuZV9pZCkgfD4KICBkcGx5cjo6ZmlsdGVyKG4gPCAzKSB8PgogIGRwbHlyOjpzZWxlY3QoLW4pIApgYGAKCgpgYGB7cn0KIyBnZXQgdG90YWwgbnVtYmVyIG9mIGNlbGxzIHBlciBmaW5hbCBhbm5vdGF0aW9uIGdyb3VwIGFuZCBzZXQgdXAgeV9sYWJlbAp0b3RhbF9jZWxsc19kZiA8LSBpbnB1dF9kb3RwbG90X2RmIHw+CiAgZHBseXI6OmNvdW50KGxhYmVsX3JlY29kZWQsIG5hbWUgPSAidG90YWxfY2VsbHMiKSB8PgogIGRwbHlyOjptdXRhdGUoeV9sYWJlbCA9IGdsdWU6OmdsdWUoIntsYWJlbF9yZWNvZGVkfSAoe3RvdGFsX2NlbGxzfSkiKSkgfD4KICBkcGx5cjo6aW5uZXJfam9pbihsYWJlbF9tYXBfZGYsIGJ5ID0gYygibGFiZWxfcmVjb2RlZCIgPSAiTkJBdGxhc19sYWJlbCIpKSB8PgogIGRwbHlyOjpncm91cF9ieShOQkF0bGFzX2ZhbWlseSkgfD4KICBkcGx5cjo6bXV0YXRlKGZhbWlseV90b3RhbF9jZWxscyA9IHN1bSh0b3RhbF9jZWxscykpIHw+CiAgIyBvcmRlciBieSBmYW1pbHkgY291bnQsIGJ1dCBwdXQgVW5rbm93biBhdCB0aGUgZW5kCiAgZHBseXI6OmFycmFuZ2UoZGVzYyhmYW1pbHlfdG90YWxfY2VsbHMpKSB8PgogICMgYW1hemluZyBuZXcgZHBseXIgdHJpY2s7IG9ubHkgdGhpcyByb3cgaXMgVFJVRSwgc28gaXQgZ2V0cyBtb3ZlZCB0byB0aGUgbGFzdCByb3cKICBkcGx5cjo6YXJyYW5nZShsYWJlbF9yZWNvZGVkID09ICJVbmtub3duIikKCnRvdGFsX2NlbGxzX2RmJHlfbGFiZWwgPC0gZmFjdG9yKHRvdGFsX2NlbGxzX2RmJHlfbGFiZWwsIGxldmVscyA9IHJldih0b3RhbF9jZWxsc19kZiR5X2xhYmVsKSkKbmJhdGxhc19iYXJfb3JkZXIgPC0gdG90YWxfY2VsbHNfZGYkbGFiZWxfcmVjb2RlZApgYGAKCmBgYHtyfQojIFByZXBhcmUgdGhlIGV4cHJlc3NlZF9nZW5lcyB2ZWN0b3IKIyB3ZSBvbmx5IGNhcmUgYWJvdXQgaWYgdGhhdCBnZW5lIGlzIGV4cHJlc3NlZCBvdGhlcndpc2Ugd2Ugd29uJ3Qgd2FzdGUgbWVtb3J5IGFuZCBpbmNsdWRlIGl0CmdlbmVfc3VtcyA8LSByb3dEYXRhKG1lcmdlZF9zY2UpIHw+CiAgYXMuZGF0YS5mcmFtZSgpIHw+CiAgZHBseXI6OnNlbGVjdChjb250YWlucygiZGV0ZWN0ZWQiKSkgfD4KICBhcy5tYXRyaXgoKSB8PgogIHJvd1N1bXMoKQpleHByZXNzZWRfZ2VuZXMgPC0gbmFtZXMoZ2VuZV9zdW1zKVtnZW5lX3N1bXMgPiAwXQpgYGAKCmBgYHtyLCBmaWcud2lkdGggPSAzMiwgZmlnLmhlaWdodCA9IDE2LCBldmFsID0gIXBhcmFtcyR0ZXN0aW5nfQpnZW5lcmF0ZV9kb3RwbG90KAogIG1lcmdlZF9zY2UgPSBtZXJnZWRfc2NlLAogIG1hcmtlcnNfZGYgPSB0b3BfbmJhdGxhc19tYXJrZXJzX2RmLAogIGNlbGx0eXBlX2RmID0gaW5wdXRfZG90cGxvdF9kZiwKICB0b3RhbF9jZWxsc19kZiA9IHRvdGFsX2NlbGxzX2RmLAogIGV4cHJlc3NlZF9nZW5lcyA9IGV4cHJlc3NlZF9nZW5lcywKICBiYXJfb3JkZXIgPSBuYmF0bGFzX2Jhcl9vcmRlciwgCiAgY2VsbHR5cGVfcGFsZXR0ZSA9IGNlbGx0eXBlX3BhbCwgCiAgbWluX2NlbGxzID0gMTAgIyBvbmx5IHNob3cgY2VsbCB0eXBlcyB3aXRoIGF0IGxlYXN0IDEwIGNlbGxzCikgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iKQpgYGAKCgojIyBFeHBvcnQKCkV4cG9ydCB0aGUgZmluYWwgdGFibGUgd2l0aCBzdWJtaXNzaW9uLXJlYWR5IGNvbHVtbiBuYW1lcy4KCmBgYHtyfQpmaW5hbF9hbm5vdGF0aW9uX2RmIHw+CiAgZHBseXI6OnNlbGVjdCgKICAgIGNlbGxfaWQsIAogICAgY2VsbF90eXBlX2Fzc2lnbm1lbnQgPSBmaW5hbF9sYWJlbCwgCiAgICB0dW1vcl9jZWxsX2NsYXNzaWZpY2F0aW9uID0gY2VsbF9jbGFzcywKICAgIENMX29udG9sb2d5X2lkID0gZmluYWxfaWQsIAogICAgQ0xfYW5ub3RhdGlvbgogICkgfD4KICByZWFkcjo6d3JpdGVfdHN2KAogICAgcGFyYW1zJGFubm90YXRpb25zX3RzdgogICkKYGBgCgojIyBTZXNzaW9uIEluZm8KCmBgYHtyfQpzZXNzaW9uSW5mbygpCmBgYAo=